/* 
 * File:   barber_shop.c
 * Author: otavio
 *
 * Created on April 17, 2011, 10:33 PM
 *
 * http://en.wikipedia.org/wiki/Sleeping_barber_problem
 */

#include <stdio.h>
#include <stdlib.h>


#include <pthread.h>
#include <unistd.h>

#define MAX_SEATS 128
/*
 * 
 */
//wait time
int s = 0;
//indicates that the waiting room is empty/full
int empty = 1, full = 0, all_customers_generated = 0;
//mutual exclusion primitive
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;

//Waiting room int array
int *wait_room = NULL;
//waiting room first/next
int first = 0, next = 0;
//thread_id of the last sericed customer
int seats = 0;

void print_waiting_room()
{
    printf("\nfirst: %d", first);
    printf("\nnext: %d", next);
    printf("\nempty: %d", empty);
    printf("\nfull: %d", full);
    int i;
    for(i = 0; i < seats; i++)
    {
        printf("\n%d: %d", i, *(wait_room + i));
    }
}

//Remove thread_id from waiting room
void leave_seat(int thr_id)
{
    if(*(wait_room + first) != thr_id)
    {
        printf("\n\nERROR: wait_room[first] != thr_id!\n\n");
        //return(EXIT_FAILURE);
    }

    *(wait_room + first++) = -1;

    if(first >= seats)
    {
        first = 0;
    }

    //check if waiting room got empty
    if(first == next)
    {
        empty = 1;
    }

    //sets full to false
    full = 0;
}


void hair_cut()
{
    //ToDo: delete customer from chairs
    if(*(wait_room + first) == -1)
    {
        printf("\n\nERROR: seats array not consistent!\n\n");
    }
    else
    {        
        int current_serviced = *(wait_room + first);
        printf("\n\nBarbeiro cortando cabelo do cliente %d", current_serviced);
        leave_seat(current_serviced);
    }    
}


//Add thread_id to waiting room
void take_seat(int thr_id)
{
    if(empty)
    {
        //seat thread in the next available seat
        *(wait_room + next) = thr_id;
        //printf("\nCliente %d sentou na cadeira %d", thr_id, next);
        empty = 0;
        
        //update next available seat number
        next++;
        if (next >= seats)
            next = 0;
    }
    else
    {
        *(wait_room + next) = thr_id;
        //printf("\nCliente %d sentou na cadeira %d", thr_id, next);
        //check if waiting room will be full after thr_id take a seat
        if(first == 0 && next == seats - 1)
        {
            //printf("\n\nFULL!!!-1");
            full = 1;
        }
        else if (first - next == 1)
        {
            //printf("\n\nFULL!!!-2");
            full = 1;
        }
        //update next available seat number
        next++;
        if (next >= seats)
            next = 0;
    }
}

//Returns true if thread_id is in waiting room
int in_waiting_room(int thr_id)
{
    int ret = 0;
    if(!empty)
    {
        int i;
        for(i = 0; i < seats; i++)
        {
            if(*(wait_room + i) == thr_id)
            {
                ret = 1;
                break;
            }
        }
    }
    return ret;
}

void *client(void *thr_id)
{
    int id = *((int *)thr_id);
//==============================================================================
    pthread_mutex_lock(&mutex1);
    if(full)
    {
        printf("\nCliente %d indo embora.", id);
        fflush(stdout);
    }
    else
    {
        //waiting = 1;
        //printf("\nCliente %d sentando", id);
        take_seat(id);
    }
    pthread_mutex_unlock(&mutex1);
//==============================================================================
    /*if(waiting)
    {
        while(in_waiting_room(id))
        {
            //printf("Cliente %d esperando\n",id);
            sleep(1);
        }
    }*/
}

void *barber()
{
    int stop = 0;
    int i = first;    
    printf("\nHello world! I am Figaro!\n");

    //wait until main thread has the time to start generating customer threads,
    //otherwhise "while" expression might fail
    sleep(1);
    //keep active while main thread generate customer threads
    //              or
    //still have customers in waiting room
    while(!stop)
    {
        if(empty)
        {
            printf("\n\nBarbeirdo dormindo");
            printf("\nSala de espera: ");
            fflush(stdout);
        }
        else
        {
//==============================================================================
            pthread_mutex_lock(&mutex1);
            hair_cut();
            pthread_mutex_unlock(&mutex1);
//==============================================================================
            printf("\nSala de espera: ");
            if(!empty)
            {
                i = first;
                int stop = 0;
                
                while(!stop)
                {
                    if(i == next)
                    {
                        stop = 1;
                    }
                    else
                    {
                        printf("%d  ", *(wait_room + i++));
                        if(i >= seats)
                        {
                            i = 0;
                        }
                    }
                }
            }
            fflush(stdout);
            s = rand() % 3 + 1;
            sleep(s);
        }

        if(all_customers_generated && empty)
        {
            printf("\n\nHora de fechar a barbearia!\n\n");
            stop = 1;
        }
    }
}

int main(int argc, char** argv)
{
     //seed the rand
    srand(time(0));

    //Check args
    if(argc == 3)
    {
        if(!strncmp(argv[1], "-s"))
        {
            seats = atoi(argv[2]);
            //allocate waiting room
            wait_room = (int *)calloc(seats, sizeof(int));
        }
        else
        {
            printf("\n\nARGS ERROR: barber_shop -s <#seats>\n\n");
            return(EXIT_FAILURE);
        }
    }

    //our dear barber, Mr. Figaro of Seville
    pthread_t figaro;
    pthread_create(&figaro, 0, barber, 0);

    //now we start launching customer threads
    int n = 0;
    for(n = 0; n < MAX_SEATS; n++)
    {
        pthread_t trh;
        pthread_create(&trh, 0, client, &n);
        sleep(1);
    }
    all_customers_generated = 1;

    pthread_join(figaro, NULL);

    return (EXIT_SUCCESS);
}

